using System;
using System.Collections.Generic;
using System.Text;
using Rapier.Kodachi.Shared;
using System.Diagnostics;
using System.Threading;

namespace Rapier.Kodachi.Engine
{
    public class Lookup
    {
        #region Properties
        public ILookupItem ActiveItem
        {
            get
            {
                if (EntryMode == EntryMode.TextEntry)
                {
                    Item textItem = new Item(null, "TextEntry", "Text Entry", "Text entered by user in text mode");
                    textItem.DefaultAction = "Display";
                    textItem.Properties["Text"] = new string(queryCharacters.ToArray());
                    textItem.GetImage = delegate() { return Resource.Text; };
                    return textItem;
                }

                if (activeIndex < 0 || activeIndex > (filteredItems.Count - 1))
                {
                    return null;
                }
                else
                {
                    return filteredItems[activeIndex];
                }
            }
        }

        /// <summary>
        /// An array of stashed items plus the active item
        /// </summary>
        public ILookupItem[] SelectedItems
        {
            get
            {
                List<ILookupItem> selectedItems = new List<ILookupItem>();
                selectedItems.AddRange(stashedItems);
                if (ActiveItem != null)
                    selectedItems.Add(ActiveItem);

                return selectedItems.ToArray();
            }
        }

        private EntryMode _EntryMode = EntryMode.Normal;
        public EntryMode EntryMode
        {
            get { return _EntryMode; }
            set
            {
                if (_EntryMode == value)
                    return;

                _EntryMode = value;
               
               if(_EntryMode == EntryMode.Normal)
                  queryCharacters.Clear();
            }
        }

        public string QueryString
        {
            get
            {
                return new string(queryCharacters.ToArray());
            }
            set
            {
                queryCharacters = new List<char>(value.ToCharArray());
            }
        }

        public ILookupItem[] AllItems
        {
            get
            {
                return allItems.ToArray();
            }
        }
        #endregion

        #region Data Members
        private Controller controller;
        private List<ILookupItem> allItems = new List<ILookupItem>();
        private List<ILookupItem> filteredItems = new List<ILookupItem>();
        private List<ILookupItem> stashedItems = new List<ILookupItem>();
        private int activeIndex = -1;
        private List<char> queryCharacters = new List<char>();
        private DateTime lastQueryCharacterEntered;
        private Dictionary<List<ILookupItem>, DrillParent> listParents = new Dictionary<List<ILookupItem>, DrillParent>();
        #endregion

        #region Constructor
        public Lookup(Controller controller)
        {
            this.controller = controller;
        }
        #endregion

        #region Public Methods
        public void AddItem(ILookupItem item)
        {
            if( allItems.Contains( item ) )
            {
                ILookupItem oldItem = allItems[allItems.IndexOf( item )];
                allItems.Remove( item );
                item.QueryMatchCount = new Dictionary<string, int>( oldItem.QueryMatchCount );
            }
            allItems.Add( item );
        }

        public void RemoveItem(ILookupItem item)
        {
            if (allItems.Contains(item))
            {
                allItems.Remove(item);
            }
        }

        public bool ItemExists(ILookupItem item)
        {
            return allItems.Contains(item);
        }

        public void AddCharacterToQuery(char c)
        {
            if (EntryMode == EntryMode.Normal)
            {
                if (controller.Settings.ClearQuery)
                {
                    TimeSpan duration = DateTime.Now - lastQueryCharacterEntered;
                    if (duration.TotalSeconds > controller.Settings.ClearQueryDuration)
                        ClearQuery();
                }
            }

            queryCharacters.Add(c);
            if (EntryMode == EntryMode.Normal)
                FilterItems();

            activeIndex = 0;
            lastQueryCharacterEntered = DateTime.Now;
            Debug.WriteLine("items: " + filteredItems.Count);
        }

        public bool ClearQuery()
        {
            bool retval = true;

            if (queryCharacters.Count == 0)
            {
                if (EntryMode == EntryMode.TextEntry)
                {
                    EntryMode = EntryMode.Normal;
                    //return true;
                }
                else
                {
                    retval = false;
                }
            }

            queryCharacters.Clear();
            ResetItems();
            return retval;
        }

        public bool StashActiveItem()
        {
            if (ActiveItem == null)
                return false;

            stashedItems.Add(ActiveItem);
            ClearQuery();
            return true;
        }

        public void Reset()
        {
            // Drill all the way up
            while (DrillUp()) { };

            ClearQuery();
            ResetItems();
            stashedItems.Clear();
            EntryMode = EntryMode.Normal;
        }

        public void ActivateItem(ItemActivationDirection direction)
        {
            if (activeIndex == -1)
                FilterItems();

            if (direction == ItemActivationDirection.Next)
            {
                activeIndex = Math.Min(activeIndex + 1, filteredItems.Count - 1);
                Debug.WriteLine("items: " + filteredItems.Count);
                Debug.WriteLine("index: " + activeIndex);
            }
            else if (direction == ItemActivationDirection.Previous)
            {
                activeIndex = Math.Max(activeIndex - 1, 0);
            }
        }

        public bool SetItem(ILookupItem lookupItem)
        {
            int i = 0;

            // Do a partial reset (don't clear stashed items or change entry mode)
            while (DrillUp()) { };
            ClearQuery();
            ResetItems();

            // Find the item
            foreach (ILookupItem item in allItems)
            {
                if (item != null && item.Equals(lookupItem))
                {
                    activeIndex = i;
                    return true;
                }
                i++;
            }

            return false;
        }
        public bool SetItem(string itemName)
        {
            int i = 0;

            // Do a partial reset (don't clear stashed items or change entry mode)
            while (DrillUp()) { };
            ClearQuery();
            ResetItems();

            // Find the item
            foreach (ILookupItem item in allItems)
            {
                if (item.Name.Equals(itemName, StringComparison.InvariantCultureIgnoreCase))
                {
                    activeIndex = i;
                    return true;
                }
                i++;
            }

            return false;
        }

        public void DrillDown(List<Plugin> plugins)
        {
            if (!(ActiveItem is Item))
                return;

            if (!(ActiveItem as Item).IsDrillable)
                return;

            List<ILookupItem> drilledItems = new List<ILookupItem>();
            (ActiveItem as Item).OnUpdateDrilledItems(drilledItems);
            listParents.Add(drilledItems, new DrillParent(queryCharacters, allItems, filteredItems, activeIndex));
            allItems = drilledItems;
            queryCharacters.Clear();
            ResetItems();
            activeIndex = 0;

            foreach (Plugin plugin in plugins)
            {
                foreach (ILookupItem item in drilledItems)
                {
                    plugin.ExtendItem(item as Item);
                }
            }
        }

        public bool DrillUp()
        {
            if (!listParents.ContainsKey(allItems))
                return false;

            List<ILookupItem> drilledItems = allItems;
            DrillParent drillParent = listParents[allItems];
            allItems = drillParent.ItemList;
            queryCharacters = drillParent.QueryCharacters;
            filteredItems = drillParent.FilteredItemList;
            activeIndex = drillParent.ActiveIndex;
            listParents.Remove(drilledItems);

            return true;
        }

        public void ResetFilteredItems()
        {
            filteredItems = new List<ILookupItem>(AllItems);
        }
        #endregion

        #region Private Methods
        private void FilterItems()
        {
            // Then we just entered query mode, make sure filtered items has the latest set of items
            if (queryCharacters.Count == 1)
            {
                ResetFilteredItems();
            }

            for (int i = filteredItems.Count - 1; i >= 0; i--)
            {
                ILookupItem item = filteredItems[i];
                if (!ItemIsMatch(item))
                {
                    filteredItems.RemoveAt(i);
                    Debug.WriteLine("removing !ItemIsMatch: " + item.Name);
                }
                else if (controller.QueryMode == QueryMode.Action)
                {
                    foreach (Item directItem in controller.DirectItems)
                    {
                        if (!(filteredItems[i] as Action).IsValidDirect(directItem))
                        {
                            filteredItems.RemoveAt(i);
                            break;
                        }
                    }
                }
                else if (controller.QueryMode == QueryMode.DirectItem)
                {
                    if (item != null && (item as Item).IndirectOnly)
                    {
                        filteredItems.RemoveAt(i);
                    }
                }
                else if (controller.QueryMode == QueryMode.IndirectItem)
                {
                    Action action = controller.Action;
                    if (action != null && !action.IsValidIndirect(item as Item))
                    {
                        filteredItems.RemoveAt(i);
                    }
                }
            }
            
            filteredItems.Sort(DoMatchCompare);
        }

        private bool ItemIsMatch(ILookupItem item)
        {
            if (item == null)
                return false;

            string remainingString = item.Name.ToUpper();
            Char cUpper;

            foreach (char c in queryCharacters)
            {
                cUpper = Char.ToUpper(c);
                if (!remainingString.Contains(cUpper.ToString()))
                    return false;

                int index = remainingString.IndexOf(cUpper);
                remainingString = remainingString.Substring(index + 1);
            }

            return true;
        }

        private void ResetItems()
        {
            activeIndex = -1;
            
            filteredItems.Clear();
            filteredItems.AddRange(allItems);
        }

        private int DoMatchCompare(ILookupItem x, ILookupItem y)
        {
            // comparison is backwards so sort will be descending
            int xScore = GetMatchScore(x);
            int yScore = GetMatchScore(y);

            return yScore.CompareTo(xScore);
        }

        private int GetMatchScore(ILookupItem item)
        {
            string queryString = QueryString.ToUpper();
            int score = item.QueryMatchCount.Count * 5;

            score += GetRelevanceScore(item);

            if (item.QueryMatchCount.ContainsKey(queryString))
                score += item.QueryMatchCount[queryString] * 10;

            if (item is Action)
            {
                if (controller.DirectItemLookup.ActiveItem != null)
                {
                    if (item.Name.Equals((controller.DirectItemLookup.ActiveItem as Item).DefaultAction, StringComparison.InvariantCultureIgnoreCase))
                        score += 25;
                }
            }

            return score;
        }

        private int GetRelevanceScore(ILookupItem item)
        {
            int score;

            string name = item.Name.ToUpper();
            string queryString = QueryString.ToUpper();

            if (name == queryString)
            {
                score = 100;
                if (ItemIsFolder(item))
                    score -= 5;
                return score;
            }

            if (name.StartsWith(queryString))
                return 90 - (name.Length - queryString.Length) - (ItemIsFolder(item) ? 5 : 0);

            int acronymnRelevance = AcronymnRelevance(item);
            if (acronymnRelevance > 0)
                return 80 + (acronymnRelevance * 2);

            if (name.Contains(queryString))
                return 70 - name.IndexOf(queryString) - (name.Length - queryString.Length) - (ItemIsFolder(item) ? 5 : 0);

            score = 60;
            int queryIndex = 0;
            foreach (char c in name.ToCharArray())
            {
                if (c != queryCharacters[queryIndex])
                    score -= (queryString.Length * 2) - (queryIndex * 2);
                else
                    queryIndex++;

                if (queryIndex == queryCharacters.Count)
                    break;
            }

            if (ItemIsFolder(item))
                score -= 5;

            return score;
        }

        private bool ItemIsFolder(ILookupItem item)
        {
            return (item is Item && (item as Item).ItemType == "Folder");
        }

        private int AcronymnRelevance(ILookupItem item)
        {
            string name = item.Name.ToUpper();
            string[] words = name.Split(' ');
            StringBuilder builder = new StringBuilder();
            foreach (string word in words)
            {
                if (String.IsNullOrEmpty(word))
                    continue;

                builder.Append(word.ToCharArray()[0]);
            }

            string acronymn = builder.ToString();
            if (acronymn.Equals(name))
                return 3;

            if (acronymn.StartsWith(name))
                return 2;

            if (acronymn.Contains(name))
                return 1;

            return 0;
        }
        #endregion
    }

    internal class DrillParent
    {
        #region Properties
        private List<char> _QueryCharacters;
        public List<char> QueryCharacters
        {
            get { return _QueryCharacters; }
            set { _QueryCharacters = value; }
        }

        private List<ILookupItem> _ItemList;
        public List<ILookupItem> ItemList
        {
            get { return _ItemList; }
            set { _ItemList = value; }
        }

        private List<ILookupItem> _FilteredItemList;
        public List<ILookupItem> FilteredItemList
        {
            get { return _FilteredItemList; }
            set { _FilteredItemList = value; }
        }

        private int _ActiveIndex;
        public int ActiveIndex
        {
            get { return _ActiveIndex; }
            set { _ActiveIndex = value; }
        }
        #endregion

        #region Constructor
        public DrillParent(List<char> queryCharacters, List<ILookupItem> itemList, List<ILookupItem> filteredItemList, int activeIndex)
        {
            _QueryCharacters = new List<char>(queryCharacters);
            _ItemList = itemList;
            _FilteredItemList = new List<ILookupItem>(filteredItemList);
            _ActiveIndex = activeIndex;
        }
        #endregion
    }

    public enum ItemActivationDirection
    {
        Next,
        Previous
    }

    public enum EntryMode
    {
        Normal,
        TextEntry
    }
}
